/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "r_local.h"


/*
 ==================
 R_PerformanceCounters
 ==================
*/
static void R_PerformanceCounters (void){

	if (r_showCull->integerValue)
		Com_Printf("in:%i clip:%i out:%i\n", tr.pc.cullIn, tr.pc.cullClip, tr.pc.cullOut);

	if (r_showUpdates->integerValue)
		Com_Printf("entityUpdates:%i lightUpdates:%i particleUpdates:%i\n", tr.pc.entityUpdates, tr.pc.lightUpdates, tr.pc.particleUpdates);

	if (r_showScene->integerValue)
		Com_Printf("entities:%i lights:%i particles:%i decals:%i\n", tr.pc.entities, tr.pc.lights, tr.pc.particles, tr.pc.decals);

	if (r_showLeafs->integerValue)
		Com_Printf("leafs:%i (surfaces:%i foliages:%i)\n", tr.pc.leafs, tr.pc.leafSurfaces, tr.pc.leafFoliages);

	if (r_showEntities->integerValue)
		Com_Printf("entities:%i (ambient:%i interaction:%i shadow:%i)\n", tr.pc.entities + tr.pc.entitiesInteraction + tr.pc.entitiesShadow, tr.pc.entities, tr.pc.entitiesInteraction, tr.pc.entitiesShadow);

	if (r_showMeshes->integerValue)
		Com_Printf("meshes:%i (ambient:%i interaction:%i shadow:%i)\n", tr.pc.meshes + tr.pc.meshesInteraction + tr.pc.meshesShadow, tr.pc.meshes, tr.pc.meshesInteraction, tr.pc.meshesShadow);

	if (r_showInteractions->integerValue)
		Com_Printf("interactions:%i (static:%i dynamic:%i) (light:%i/%i fog:%i/%i)\n", tr.pc.meshesInteraction, tr.pc.interactionsSLight + tr.pc.interactionsSFog, tr.pc.interactionsDLight + tr.pc.interactionsDFog, tr.pc.interactionsSLight, tr.pc.interactionsDLight, tr.pc.interactionsSFog, tr.pc.interactionsDFog);

	if (r_showShadows->integerValue)
		Com_Printf("shadows:%i (static:%i dynamic:%i) (zpass:%i/%i zfail:%i/%i)\n", tr.pc.meshesShadow, tr.pc.shadowsSZPass + tr.pc.shadowsSZFail, tr.pc.shadowsDZPass + tr.pc.shadowsDZFail, tr.pc.shadowsSZPass, tr.pc.shadowsDZPass, tr.pc.shadowsSZFail, tr.pc.shadowsDZFail);

	if (r_showLights->integerValue)
		Com_Printf("lights:%i (static:%i dynamic:%i)\n", tr.pc.lights, tr.pc.lightsStatic, tr.pc.lightsDynamic);

	if (r_showDynamic->integerValue)
		Com_Printf("tris:%i verts:%i (md5:%i foliage:%i sprite:%i beam:%i particle:%i decal:%i)\n", tr.pc.dynamicIndices / 3, tr.pc.dynamicVertices, tr.pc.dynamicMD5, tr.pc.dynamicFoliages, tr.pc.dynamicSprites, tr.pc.dynamicBeams, tr.pc.dynamicParticles, tr.pc.dynamicDecals);

	if (r_showDeforms->integerValue)
		Com_Printf("tris:%i verts:%i (expand:%i move:%i sprite:%i tube:%i flare:%i)\n", tr.pc.deformIndices / 3, tr.pc.deformVertices, tr.pc.deformExpands, tr.pc.deformMoves, tr.pc.deformSprites, tr.pc.deformTubes, tr.pc.deformFlares);

	if (r_showPrimitives->integerValue)
		Com_Printf("views:%i draws:%i tris:%i (shdw:%i) verts:%i (shdw:%i)\n", tr.pc.views, tr.pc.draws, (tr.pc.indices + tr.pc.shadowIndices) / 3, tr.pc.shadowIndices / 3, tr.pc.vertices + tr.pc.shadowVertices, tr.pc.shadowVertices);

	if (r_showVertexBuffers->integerValue)
		Com_Printf("vertex buffers:%i=%ik (static:%i=%ik dynamic:%i=%ik)\n", tr.pc.staticBuffers + tr.pc.dynamicBuffers, (tr.pc.staticBufferBytes + tr.pc.dynamicBufferBytes) >> 10, tr.pc.staticBuffers, tr.pc.staticBufferBytes >> 10, tr.pc.dynamicBuffers, tr.pc.dynamicBufferBytes >> 10);

	if (r_showTextureUsage->integerValue)
		Com_Printf("textures:%i (%.2f MB)\n", tr.pc.textures, tr.pc.textureBytes * (1.0f/1048576));

	if (r_showRenderToTexture->integerValue)
		Com_Printf("copyToTextures:%i (%i kpix) dynamicTextures:%i (%i kpix)\n", tr.pc.copyToTextures, tr.pc.copyToTexturePixels / 1000, tr.pc.dynamicTextures, tr.pc.dynamicTexturePixels / 1000);

	if (r_showOverdraw->integerValue)
		Com_Printf("overdraw: %.2f\n", tr.pc.overdraw);

	if (r_showShadowCount->integerValue)
		Com_Printf("shadows overdraw: %.2f\n", tr.pc.overdrawShadows);

	if (r_showLightCount->integerValue)
		Com_Printf("lights overdraw: %.2f\n", tr.pc.overdrawLights);

	// Clear for next frame
	memset(&tr.pc, 0, sizeof(performanceCounters_t));
}

/*
 ==================
 R_IssueRenderCommands
 ==================
*/
static void R_IssueRenderCommands (void){

	renderCommandList_t	*commandList = &backEnd.commandList;

	if (!commandList->size)
		return;

	// Add an end of list command
	*(int *)(commandList->data + commandList->size) = RC_END_OF_LIST;

	// Clear it out
	commandList->size = 0;
	commandList->overflowed = false;

	// Execute the commands
	RB_ExecuteRenderCommands(commandList->data);

	// Look at the performance counters
	R_PerformanceCounters();
}

/*
 ==================
 R_GetCommandBuffer
 ==================
*/
static void *R_GetCommandBuffer (int size){

	renderCommandList_t	*commandList = &backEnd.commandList;

	if (commandList->overflowed)
		return NULL;		// Already overflowed

	// Always leave room for the end of list command
	if (commandList->size + size + sizeof(int) > MAX_RENDER_COMMANDS){
		Com_Printf(S_COLOR_YELLOW "R_GetCommandBuffer: overflow\n");

		commandList->overflowed = true;

		return NULL;
	}

	commandList->size += size;

	return commandList->data + commandList->size - size;
}


// ============================================================================


/*
 ==================
 R_AddRenderViewCommand
 ==================
*/
void R_AddRenderViewCommand (bool bloom){

	renderViewCommand_t	*cmd;

	// Add a render view command
	cmd = (renderViewCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_RENDER_VIEW;

	cmd->time = tr.renderView.time;
	memcpy(cmd->materialParms, tr.renderView.materialParms, MAX_MATERIAL_PARMS * sizeof(float));

	cmd->scene = tr.scene;

	// View parms
	cmd->viewParms.primaryView = tr.viewParms.primaryView;
	cmd->viewParms.viewType = tr.viewParms.viewType;

	cmd->viewParms.bloom = bloom;

	cmd->viewParms.x = tr.viewParms.viewportX;
	cmd->viewParms.y = tr.viewParms.viewportY;
	cmd->viewParms.width = tr.viewParms.viewportWidth;
	cmd->viewParms.height = tr.viewParms.viewportHeight;

	cmd->viewParms.origin = tr.renderView.origin;
	cmd->viewParms.axis = tr.renderView.axis;

	cmd->viewParms.projectionMatrix = tr.viewParms.projectionMatrix;
	cmd->viewParms.modelviewMatrix = tr.viewParms.modelviewMatrix;
	cmd->viewParms.skyBoxMatrix = tr.viewParms.skyBoxMatrix;
	cmd->viewParms.portalSkyMatrix = tr.viewParms.portalSkyMatrix;
	cmd->viewParms.screenMatrix = tr.viewParms.screenMatrix;

	// Draw mesh lists
	cmd->numDrawMeshes = tr.viewParms.numDrawMeshes;
	cmd->drawMeshes = tr.viewParms.drawMeshes;

	cmd->numDrawPostProcessMeshes = tr.viewParms.numDrawPostProcessMeshes;
	cmd->drawPostProcessMeshes = tr.viewParms.drawPostProcessMeshes;

	// Draw light lists
	cmd->numDrawLights = tr.viewParms.numDrawLights;
	cmd->drawLights = tr.viewParms.drawLights;

	cmd->numDrawFogLights = tr.viewParms.numDrawFogLights;
	cmd->drawFogLights = tr.viewParms.drawFogLights;
}

/*
 ==================
 R_AddRenderBloomCommand
 ==================
*/
void R_AddRenderBloomCommand (void){

	renderBloomCommand_t	*cmd;

	// Add a render bloom command
	cmd = (renderBloomCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_RENDER_BLOOM;
}

/*
 ==================
 R_AddCompositeBloomCommand
 ==================
*/
void R_AddCompositeBloomCommand (void){

	compositeBloomCommand_t	*cmd;

	// Add a composite bloom command
	cmd = (compositeBloomCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_COMPOSITE_BLOOM;
}

/*
 ==================
 R_CaptureRenderToTexture
 ==================
*/
bool R_CaptureRenderToTexture (const char *name){

	captureRenderCommand_t	*cmd;
	texture_t				*texture;

	// Get the texture
	texture = R_GetTextureByName(name);
	if (!texture){
		Com_DPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: couldn't find '%s'\n", name);
		return false;
	}

	// Make sure it's not a cube map
	if (texture->isCubeMap){
		Com_DPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: '%s' is a cube map\n", name);
		return false;
	}

	// Make sure it may not be down-sized or compressed
	if (!(texture->flags & (TF_NOPICMIP | TF_UNCOMPRESSED))){
		Com_DPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: '%s' may be down-sized or compressed\n", name);
		return false;
	}

	// Make sure it's not mipmapped
	if (texture->filter == TF_DEFAULT){
		Com_DPrintf(S_COLOR_YELLOW "R_CaptureRenderToTexture: '%s' has mipmaps\n", name);
		return false;
	}

	// Add a capture render command
	cmd = (captureRenderCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return true;

	cmd->commandId = RC_CAPTURE_RENDER;

	cmd->texture = texture;

	return true;
}

/*
 ==================
 R_UpdateTextureImage
 ==================
*/
bool R_UpdateTextureImage (const char *name, const byte *image, int width, int height){

	updateTextureCommand_t	*cmd;
	texture_t				*texture;

	// Get the texture
	texture = R_GetTextureByName(name);
	if (!texture){
		Com_DPrintf(S_COLOR_YELLOW "R_UpdateTextureImage: couldn't find '%s'\n", name);
		return false;
	}

	// Make sure it's not a cube map
	if (texture->isCubeMap){
		Com_DPrintf(S_COLOR_YELLOW "R_UpdateTextureImage: '%s' is a cube map\n", name);
		return false;
	}

	// Make sure it may not be down-sized or compressed
	if (!(texture->flags & (TF_NOPICMIP | TF_UNCOMPRESSED))){
		Com_DPrintf(S_COLOR_YELLOW "R_UpdateTextureImage: '%s' may be down-sized or compressed\n", name);
		return false;
	}

	// Make sure it's not mipmapped
	if (texture->filter == TF_DEFAULT){
		Com_DPrintf(S_COLOR_YELLOW "R_UpdateTextureImage: '%s' has mipmaps\n", name);
		return false;
	}

	// Add an update texture command
	cmd = (updateTextureCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return true;

	cmd->commandId = RC_UPDATE_TEXTURE;

	cmd->texture = texture;

	cmd->image = image;
	cmd->width = width;
	cmd->height = height;

	return true;
}

/*
 ==================
 R_SetColor
 ==================
*/
void R_SetColor (const odVec4 &color){

	setColorCommand_t	*cmd;

	// Add a set color command
	cmd = (setColorCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_SET_COLOR;

	cmd->color[0] = color[0];
	cmd->color[1] = color[1];
	cmd->color[2] = color[2];
	cmd->color[3] = color[3];
}

/*
 ==================
 R_SetColor4
 ==================
*/
void R_SetColor4 (float r, float g, float b, float a){

	setColorCommand_t	*cmd;

	// Add a set color command
	cmd = (setColorCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_SET_COLOR;

	cmd->color[0] = r;
	cmd->color[1] = g;
	cmd->color[2] = b;
	cmd->color[3] = a;
}

/*
 ==================
 R_DrawStretchPic
 ==================
*/
void R_DrawStretchPic (float x, float y, float w, float h, float s1, float t1, float s2, float t2, material_t *material){

	stretchPicCommand_t	*cmd;
	float				xScale, yScale;

	if (!material)
		Com_Error(false, "R_DrawStretchPic: NULL material");

	// Get scale for adjusting coordinates
	xScale = tr.renderCrops[tr.currentRenderCrop].xScale;
	yScale = tr.renderCrops[tr.currentRenderCrop].yScale;

	// Add a stretch pic command
	cmd = (stretchPicCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_STRETCH_PIC;

	cmd->x = x * xScale;
	cmd->y = y * yScale;
	cmd->w = w * xScale;
	cmd->h = h * yScale;
	cmd->s1 = s1;
	cmd->t1 = t1;
	cmd->s2 = s2;
	cmd->t2 = t2;

	cmd->material = material;
}

/*
 ==================
 R_DrawShearedPic
 ==================
*/
void R_DrawShearedPic (float x, float y, float w, float h, float s1, float t1, float s2, float t2, float xShear, float yShear, material_t *material){

	shearedPicCommand_t	*cmd;
	float				xScale, yScale;

	if (!material)
		Com_Error(false, "R_DrawShearedPic: NULL material");

	// Get scale for adjusting coordinates
	xScale = tr.renderCrops[tr.currentRenderCrop].xScale;
	yScale = tr.renderCrops[tr.currentRenderCrop].yScale;

	// Add a sheared pic command
	cmd = (shearedPicCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_SHEARED_PIC;

	cmd->x = x * xScale;
	cmd->y = y * yScale;
	cmd->w = w * xScale;
	cmd->h = h * yScale;
	cmd->s1 = s1;
	cmd->t1 = t1;
	cmd->s2 = s2;
	cmd->t2 = t2;
	cmd->xShear = xShear * xScale;
	cmd->yShear = yShear * yScale;

	cmd->material = material;
}

/*
 ==================
 R_SetRenderSize
 ==================
*/
void R_SetRenderSize (void){

	renderSizeCommand_t	*cmd;
	int					width, height;

	// Calculate the crop size
	if (tr.envShotRendering){
		width = tr.envShotSize;
		height = tr.envShotSize;
	}
	else {
		width = glConfig.videoWidth;
		height = glConfig.videoHeight;

		if (r_screenFraction->floatValue > 0.0f && r_screenFraction->floatValue < 1.0f){
			width = M_FtolFast(width * r_screenFraction->floatValue);
			height = M_FtolFast(height * r_screenFraction->floatValue);
		}

		width &= ~1;
		height &= ~1;
	}

	// Set up the current render crop
	tr.currentRenderCrop = 0;

	tr.renderCrops[tr.currentRenderCrop].width = width;
	tr.renderCrops[tr.currentRenderCrop].height = height;

	tr.renderCrops[tr.currentRenderCrop].xScale = width * (1.0f / SCREEN_WIDTH);
	tr.renderCrops[tr.currentRenderCrop].yScale = height * (1.0f / SCREEN_HEIGHT);

	// Add a render size command
	cmd = (renderSizeCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_RENDER_SIZE;

	cmd->width = tr.renderCrops[tr.currentRenderCrop].width;
	cmd->height = tr.renderCrops[tr.currentRenderCrop].height;
}

/*
 ==================
 R_CropRenderSize
 ==================
*/
void R_CropRenderSize (int width, int height, bool makePowerOfTwo){

	renderSizeCommand_t	*cmd;

	if ((width < 1 || width > SCREEN_WIDTH) || (height < 1 || height > SCREEN_HEIGHT))
		Com_Error(false, "R_CropRenderSize: bad size (%i x %i)", width, height);

	// Calculate the crop size
	width = M_FtolFast(width * tr.renderCrops[tr.currentRenderCrop].xScale);
	height = M_FtolFast(height * tr.renderCrops[tr.currentRenderCrop].yScale);

	if (makePowerOfTwo && !glConfig.textureNonPowerOfTwoAvailable){
		width = M_FloorPowerOfTwo(width);
		height = M_FloorPowerOfTwo(height);
	}
	else {
		width &= ~1;
		height &= ~1;
	}

	// Set up the current render crop
	tr.currentRenderCrop++;

	if (tr.currentRenderCrop == MAX_RENDER_CROPS)
		Com_Error(false, "R_CropRenderSize: MAX_RENDER_CROPS hit");

	tr.renderCrops[tr.currentRenderCrop].width = width;
	tr.renderCrops[tr.currentRenderCrop].height = height;

	tr.renderCrops[tr.currentRenderCrop].xScale = width * (1.0f / SCREEN_WIDTH);
	tr.renderCrops[tr.currentRenderCrop].yScale = height * (1.0f / SCREEN_HEIGHT);

	// Add a render size command
	cmd = (renderSizeCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_RENDER_SIZE;

	cmd->width = tr.renderCrops[tr.currentRenderCrop].width;
	cmd->height = tr.renderCrops[tr.currentRenderCrop].height;
}

/*
 ==================
 R_UnCrop
 ==================
*/
void R_UnCrop (void){

	renderSizeCommand_t	*cmd;

	// Set up the current render crop
	if (tr.currentRenderCrop < 1)
		Com_Error(false, "R_UnCrop: currentRenderCrop < 1");

	tr.currentRenderCrop--;

	// Add a render size command
	cmd = (renderSizeCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (!cmd)
		return;

	cmd->commandId = RC_RENDER_SIZE;

	cmd->width = tr.renderCrops[tr.currentRenderCrop].width;
	cmd->height = tr.renderCrops[tr.currentRenderCrop].height;
}

/*
 ==================
 R_DrawEditBox

 For integrated editors
 ==================
*/
void R_DrawEditBox (const odVec3 &color, const odVec3 corners[8]){

	if (!backEnd.debugTools)
		return;

	// Set the GL state
	GL_LoadMatrix(GL_MODELVIEW, backEnd.viewParms.modelviewMatrix);

	GL_DisableTexture();

	GL_Disable(GL_CULL_FACE);
	GL_Disable(GL_POLYGON_OFFSET_FILL);
	GL_Disable(GL_BLEND);
	GL_Disable(GL_ALPHA_TEST);
	GL_Disable(GL_DEPTH_TEST);
	GL_Disable(GL_STENCIL_TEST);

	GL_ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	GL_DepthMask(GL_FALSE);
	GL_StencilMask(0);

	GL_DepthRange(0.0f, 1.0f);

	qglColor3fv(color.ToFloatPtr());

	// Draw it
	qglBegin(GL_LINE_LOOP);
	qglVertex3fv(corners[0].ToFloatPtr());
	qglVertex3fv(corners[2].ToFloatPtr());
	qglVertex3fv(corners[3].ToFloatPtr());
	qglVertex3fv(corners[1].ToFloatPtr());
	qglEnd();

	qglBegin(GL_LINE_LOOP);
	qglVertex3fv(corners[4].ToFloatPtr());
	qglVertex3fv(corners[6].ToFloatPtr());
	qglVertex3fv(corners[7].ToFloatPtr());
	qglVertex3fv(corners[5].ToFloatPtr());
	qglEnd();

	qglBegin(GL_LINES);
	qglVertex3fv(corners[0].ToFloatPtr());
	qglVertex3fv(corners[4].ToFloatPtr());
	qglVertex3fv(corners[1].ToFloatPtr());
	qglVertex3fv(corners[5].ToFloatPtr());
	qglVertex3fv(corners[2].ToFloatPtr());
	qglVertex3fv(corners[6].ToFloatPtr());
	qglVertex3fv(corners[3].ToFloatPtr());
	qglVertex3fv(corners[7].ToFloatPtr());
	qglEnd();

	// Check for errors
	if (!r_ignoreGLErrors->integerValue)
		GL_CheckForErrors();
}

/*
 ==================
 R_BeginFrame
 ==================
*/
void R_BeginFrame (int frameTime){

	setBuffersCommand_t		*cmd;

	// Set frame time
	tr.frameTime = frameTime;

	// Bump frame count
	tr.frameCount++;

	// Log file
	if (r_logFile->modified){
		QGL_EnableLogging(r_logFile->integerValue);

		r_logFile->modified = false;
	}

	// Set default GL state
	GL_SetDefaultState();

	// Free light memory
	R_FreeLightMemory();

	// Reset view
	R_ResetView();

	// Set the render size
	R_SetRenderSize();

	// Update gamma
	if (r_gamma->modified || r_brightness->modified)
		R_SetGamma();

	// Update texture parameters
	if (r_textureFilter->modified || r_textureLODBias->modified || r_textureAnisotropy->modified)
		R_SetTextureParameters();

	// Add a set buffers command
	cmd = (setBuffersCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (cmd)
		cmd->commandId = RC_SET_BUFFERS;
}

/*
 ==================
 R_EndFrame
 ==================
*/
void R_EndFrame (void){

	swapBuffersCommand_t	*cmd;
	int						i;

	// Add a swap buffers command
	cmd = (swapBuffersCommand_t *)R_GetCommandBuffer(sizeof(*cmd));
	if (cmd)
		cmd->commandId = RC_SWAP_BUFFERS;

	// Issue all commands
	R_IssueRenderCommands();

	// Allow render scenes to be modified again
	for (i = 0; i < MAX_RENDER_SCENES; i++){
		if (!tr.renderScenes[i])
			continue;

		tr.renderScenes[i]->inUse = false;
	}

	// Log file
	if (r_logFile->integerValue > 0)
		CVar_SetInteger(r_logFile, r_logFile->integerValue - 1);
}
